<template>
  <div class="shapefile-parser">
    <div class="panel">
      <input ref="file" type="file" multiple accept=".shp,.dbf,.prj,.shx">
      <button @click="handleSelect">确认</button>
    </div>
    <div ref="map" class="map-box" />
  </div>
</template>

<script>
import 'ol/ol.css'
import { Map, View } from 'ol'
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
import { OSM, Vector as VectorSource } from 'ol/source'
import { Fill, Stroke, Style } from 'ol/style'
import { GeoJSON } from 'ol/format'
// import GeometryType from 'ol/geom/GeometryType'
import { GeoJSON } from 'ol/format'
// eslint-disable-next-line no-unused-vars
import { open as shapeOpen, read as shapeRead } from 'shapefile'
import proj4 from 'proj4'
import { createTransformFromCoordinateTransform } from 'ol/proj'

const GeometryType = {
  POINT: 'Point',
  LINE_STRING: 'LineString',
  LINEAR_RING: 'LinearRing',
  POLYGON: 'Polygon',
  MULTI_POINT: 'MultiPoint',
  MULTI_LINE_STRING: 'MultiLineString',
  MULTI_POLYGON: 'MultiPolygon',
  GEOMETRY_COLLECTION: 'GeometryCollection',
  CIRCLE: 'Circle'
}

export default {
  name: 'ShapefileParser',
  data() {
    return {
      map: undefined,
      shapeLayer: undefined
    }
  },
  mounted() {
    this.initMap()
  },
  methods: {
    initMap() {
      // 初始化地图
      const map = this.map = new Map({
        layers: [new TileLayer({ source: new OSM() })],
        view: new View({
          // center: [120, 30],
          center: [13358338.895192828, 3503549.843504374],
          zoom: 8,
          // projection: 'EPSG:4326'
          projection: 'EPSG:3857'
        })
      })
      map.setTarget(this.$refs.map)
      this.initShapeLayer(map)
    },
    initShapeLayer(map) {
      // 创建 shapefile 加载后的显示图层
      let vectorSource
      if (this.shapeLayer) {
        vectorSource = this.shapeLayer.getSource()
        vectorSource.clear()
      } else {
        vectorSource = new VectorSource()
        const vectorLayer = new VectorLayer({
          source: vectorSource,
          style: feature => {
            const type = feature.getGeometry().getType()

            // 根据图形类型绘制不同颜色
            let color = '#2b76d8'
            switch (type) {
              case GeometryType.POLYGON:
                color = '#8b15b9'
                break
              case GeometryType.MULTI_POLYGON:
                color = '#e1146b'
                break
            }

            return new Style({
              stroke: new Stroke({
                color: `${color}`,
                width: 3
              }),
              fill: new Fill({ color: `${color}88` }) // #e1146b88 88是透明度
            })
          },
          zIndex: 99
        })
        this.shapeLayer = vectorLayer
        vectorLayer.setMap(map)
      }
    },
    handleSelect() {
      let files = this.$refs.file.files
      console.log(files, typeof files, 'files FileList')
      // files = Array.from(new Array(files.length), (i, idx) => files[idx]) // 等效下面写法
      files = Array.from(files) // FileList => Array, 方便使用 Array 方法
      console.log(files, typeof files, 'files Array')

      // 解析 shp
      this.parseShapefile(files) // 解析选择的 shp 并绘制显示
    },
    parseShapefile(files) {
      const vectorSource = this.shapeLayer.getSource()
      const shpFile = files.find(f => f.name.endsWith('.shp'))
      const dbfFile = files.find(f => f.name.endsWith('.dbf'))
      const prjFile = files.find(f => f.name.endsWith('.prj'))

      // 可使用 Promise.all 优化为下面写法
      // this.readInputFile(shpFile, 'ArrayBuffer').then(async(fileData) => {
      //   const featureJsons = []
      //
      //   await shapeOpen(fileData).then(async(source) => {
      //     await source.read().then(function log(result) {
      //       if (result.done) {
      //         return
      //       }
      //       featureJsons.push(result.value)
      //       console.log(result.value)
      //       return source.read().then(log)
      //     })
      //   }).catch(error => console.error(error.stack))
      //
      //   console.log(featureJsons, typeof featureJsons, 'featureJsons')
      //   const features = featureJsons.map(j => new GeoJSON().readFeature(j))
      //   console.log(features, typeof features, 'features')
      //
      //   vectorSource.addFeatures(features)
      //   this.mapFit(vectorSource.getExtent(), this.map)
      // }).catch(e => {
      //   console.log(e, typeof e, 'l e')
      // })

      const promises = [shpFile, dbfFile].map(i => this.readInputFile(i))
      promises.push(this.readInputFile(prjFile, 'Text'))

      let prjCrs
      Promise.all(promises).then(([shp, dbf, prj]) => {
        console.log(prj, typeof prj, 'prj')
        prjCrs = new proj4.Proj(prj)
        console.log(prjCrs, typeof prjCrs, 'prjCrs')

        // return shapeOpen(shp, dbf)
        // 指定 dbf 编码 'utf-8', 解决geojson properties乱码
        return shapeRead(shp, dbf, { encoding:'utf-8' }) 
      }).then(async(source) => {
        // shapefile 提供了 read 方法直接返回一个 FeatureCollection geojson
        // const featureJsons = []
        // await source.read().then(function log(result) {
        //   if (result.done) {
        //     return
        //   }
        //   featureJsons.push(result.value)
        //   console.log(result.value)
        //   return source.read().then(log)
        // })
        // return featureJsons

        console.log(source, typeof source, 'shapeRead source')
        return source.features
      }).then((featureJsons) => {
        console.log(featureJsons, typeof featureJsons, 'featureJsons')
        const forward = proj4(prjCrs, 'EPSG:3857').forward // proj4 提供的坐标系的转换
        // forward([120, 30]) // => 13358338.895192828, 3503549.843504374

        // createTransformFromCoordinateTransform 从一个点的坐标系转换函数 构造一个转换函数
        const transformFn = createTransformFromCoordinateTransform(forward)
        const features = featureJsons.map(j => {
          const feature = new GeoJSON().readFeature(j)
          feature.getGeometry().applyTransform(transformFn)
          return feature
        })
        console.log(features, typeof features, 'features')

        vectorSource.addFeatures(features)
        this.mapFit(vectorSource.getExtent(), this.map)
      }).catch(error => console.error(error.stack))
    },
    async readInputFile(file, type = 'ArrayBuffer') {
      // 读取文件
      return new Promise((resolve, reject) => {
        const reader = new FileReader()
        switch (type) {
          case 'ArrayBuffer':
            reader.readAsArrayBuffer(file)
            break
          case 'Text':
            reader.readAsText(file)
            break
          case 'BinaryString':
            reader.readAsBinaryString(file)
            break
          case 'DataURL':
            reader.readAsDataURL(file)
            break
        }

        reader.onload = function() {
          // this.result 就是读取到的文件的数据
          resolve(this.result)
        }

        reader.onerror = function() {
          reject(this)
        }
      })
    },
    mapFit(extent, mapInstance) {
      let [width, height] = mapInstance.getSize()
      width = Math.ceil(width / 5)
      height = Math.ceil(height / 5)

      const view = mapInstance.getView()
      view.fit(extent, {
        padding: [height, width, height, width],
        duration: 500,
        maxZoom: view.getMaxZoom() - 2
      })
    }
  }
}
</script>

<style scoped lang="scss">
.shapefile-parser {
  width: 1000px;
  height: 700px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;

  .panel {
    padding: 10px 0;
  }

  .map-box {
    flex: 1;
    border: 1px solid #e3e3e3;
  }
}
</style>
